﻿<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
  
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>How-to: Build your own type of documentation with a custom plug-in | DocFX website </title>
    <meta name="viewport" content="width=device-width">
    <meta name="title" content="How-to: Build your own type of documentation with a custom plug-in | DocFX website ">
    <meta name="generator" content="docfx 2.37.0.0">
    
    <link rel="shortcut icon" href="../favicon.ico">
    <link rel="stylesheet" href="../styles/docfx.vendor.css">
    <link rel="stylesheet" href="../styles/docfx.css">
    <link rel="stylesheet" href="../styles/main.css">
    <meta property="docfx:navrel" content="../toc.html">
    <meta property="docfx:tocrel" content="toc.html">
    
    <meta property="docfx:rel" content="../">
    
  </head>
  <body data-spy="scroll" data-target="#affix" data-offset="120">
    <div id="wrapper">
      <header>
        
        <nav id="autocollapse" class="navbar navbar-inverse ng-scope" role="navigation">
          <div class="container">
            <div class="navbar-header">
              <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              
              <a class="navbar-brand" href="../index.html">
                <img id="logo" class="svg" src="../logo.svg" alt="">
              </a>
            </div>
            <div class="collapse navbar-collapse" id="navbar">
              <form class="navbar-form navbar-right" role="search" id="search">
                <div class="form-group">
                  <input type="text" class="form-control" id="search-query" placeholder="Search" autocomplete="off">
                </div>
              </form>
            </div>
          </div>
        </nav>
        
        <div class="subnav navbar navbar-default">
          <div class="container hide-when-search" id="breadcrumb">
            <ul class="breadcrumb">
              <li></li>
            </ul>
          </div>
        </div>
      </header>
      <div class="container body-content">
        
        <div id="search-results">
          <div class="search-list"></div>
          <div class="sr-items">
            <p><i class="glyphicon glyphicon-refresh index-loading"></i></p>
          </div>
          <ul id="pagination"></ul>
        </div>
      </div>
      <div role="main" class="container body-content hide-when-search">
        
        <div class="sidenav hide-when-search">
          <a class="btn toc-toggle collapse" data-toggle="collapse" href="#sidetoggle" aria-expanded="false" aria-controls="sidetoggle">Show / Hide Table of Contents</a>
          <div class="sidetoggle collapse" id="sidetoggle">
            <div id="sidetoc"></div>
          </div>
        </div>
        <div class="article row grid-right">
          <div class="col-md-10">
            <article class="content wrap" id="_content" data-uid="">
<h1 id="how-to-build-your-own-type-of-documentation-with-a-custom-plug-in">How-to: Build your own type of documentation with a custom plug-in</h1>

<p>In this topic we will create a plug-in to convert some simple <a href="https://en.wikipedia.org/wiki/Rich_Text_Format">rich text format</a> files to html documents.</p>
<h2 id="goal-and-limitation">Goal and limitation</h2>
<ol>
<li>In scope:
<ol>
<li>Our input will be a set of rtf files with <code>.rtf</code> as the file extension name.</li>
<li>The rtf files will be built as html document.</li>
</ol>
</li>
<li>Out of scope:
<ol>
<li>Picture or other object in rtf files.</li>
<li>Hyperlink in rtf files. (in the <a href="advanced_support_hyperlink.html">advanced tutorial</a>, we will describe how to support hyperlinks in a custom plugin.)</li>
<li>Metadata and title.</li>
</ol>
</li>
</ol>
<h2 id="preparation">Preparation</h2>
<ol>
<li><p>Create a new C# class library project in <code>Visual Studio</code>, targets .NET Framework 4.6.1.</p>
</li>
<li><p>Add nuget packages:</p>
<ul>
<li><code>System.Collections.Immutable</code> with version 1.3.1</li>
<li><code>Microsoft.Composition</code> with version 1.0.31</li>
</ul>
</li>
<li><p>Add <code>Microsoft.DocAsCode.Plugins</code> and <code>Microsoft.DocAsCode.Common</code>
If building DocFX from source code then add a reference to the project,
otherwise add the nuget packages with the same version as DocFX.</p>
</li>
<li><p>Add framework assembly references:
<code>PresentationCore</code>, <code>PresentationFramework</code>, <code>WindowsBase</code>. (This step is optional in Visual Studio 2017)</p>
</li>
<li><p>Add a project for converting rtf to html:<br>
Clone project <a href="https://github.com/mmanela/MarkupConverter">MarkupConverter</a>, and reference it.</p>
</li>
<li><p>Copy the code file <code>C#,C++,F#,VB\ParallelExtensionsExtras\TaskSchedulers\StaTaskScheduler.cs</code> from <a href="https://code.msdn.microsoft.com/ParExtSamples">ParExtSamples</a></p>
</li>
</ol>
<h2 id="create-a-document-processor">Create a document processor</h2>
<h3 id="responsibility-of-the-document-processor">Responsibility of the document processor</h3>
<ul>
<li>Declare which file can be handled.</li>
<li>Load from the file to the object model.</li>
<li>Provide build steps.</li>
<li>Report document type, file links and xref links in document.</li>
<li>Update references.</li>
</ul>
<h3 id="create-our-rtfdocumentprocessor">Create our RtfDocumentProcessor</h3>
<ol>
<li><p>Create a new class (RtfDocumentProcessor.cs) with the following code:</p>
<pre><code class="lang-csharp">[Export(typeof(IDocumentProcessor))]
public class RtfDocumentProcessor : IDocumentProcessor
{
    // todo : implements IDocumentProcessor.
}
</code></pre>
</li>
<li><p>Declare that we can handle the <code>.rtf</code> file:</p>
<pre><code class="lang-csharp" name="GetProcessingPriority">public ProcessingPriority GetProcessingPriority(FileAndType file)
{
    if (file.Type == DocumentType.Article &amp;&amp;
        &quot;.rtf&quot;.Equals(Path.GetExtension(file.File), StringComparison.OrdinalIgnoreCase))
    {
        return ProcessingPriority.Normal;
    }
    return ProcessingPriority.NotSupported;
}
</code></pre>
<p>Here we declare this processor can handle any <code>.rtf</code> file in the article category with normal priority.
When two or more processors compete for the same file, DocFX will give it to the higher priority one.
<em>Unexpected</em>: two or more processor declare for the same file with same priority.</p>
</li>
<li><p>Load our rtf file by reading all text:</p>
<pre><code class="lang-csharp" name="Load">public FileModel Load(FileAndType file, ImmutableDictionary&lt;string, object&gt; metadata)
{
    var content = new Dictionary&lt;string, object&gt;
    {
        [&quot;conceptual&quot;] = File.ReadAllText(Path.Combine(file.BaseDir, file.File)),
        [&quot;type&quot;] = &quot;Conceptual&quot;,
        [&quot;path&quot;] = file.File,
    };
    var localPathFromRoot = PathUtility.MakeRelativePath(EnvironmentContext.BaseDirectory, EnvironmentContext.FileAbstractLayer.GetPhysicalPath(file.File));

    return new FileModel(file, content)
    {
        LocalPathFromRoot = localPathFromRoot,
    };
}
</code></pre>
<p>We use <code>Dictionary&lt;string, object&gt;</code> as the data model, similar to how <a href="https://github.com/dotnet/docfx/blob/dev/src/Microsoft.DocAsCode.EntityModel/Plugins/ConceptualDocumentProcessor.cs">ConceptualDocumentProcessor</a> stores the content of markdown files.</p>
</li>
<li><p>Implement <code>Save</code> method as follows:</p>
<pre><code class="lang-csharp" name="Save">public SaveResult Save(FileModel model)
{
    return new SaveResult
    {
        DocumentType = &quot;Conceptual&quot;,
        FileWithoutExtension = Path.ChangeExtension(model.File, null),
    };
}
</code></pre></li>
<li><p><code>BuildSteps</code> property can provide several build steps for the model. We suggest implementing this in the following manner:</p>
<pre><code class="lang-csharp" name="BuildSteps">[ImportMany(nameof(RtfDocumentProcessor))]
public IEnumerable&lt;IDocumentBuildStep&gt; BuildSteps { get; set; }
</code></pre></li>
<li><p><code>Name</code> property is used to display in the log, so give any constant string you like.<br>
e.g.:</p>
<pre><code class="lang-csharp" name="Name">public string Name =&gt; nameof(RtfDocumentProcessor);
</code></pre></li>
<li><p>Since we don't support hyperlink, keep the <code>UpdateHref</code> method empty.</p>
<pre><code class="lang-csharp" name="UpdateHref">public void UpdateHref(FileModel model, IDocumentBuildContext context)
{
}
</code></pre></li>
</ol>
<p>View the final <a href="../codesnippet/Rtf/RtfDocumentProcessor.cs">RtfDocumentProcessor.cs</a></p>
<h2 id="create-a-document-build-step">Create a document build step</h2>
<h3 id="responsibility-of-the-build-step">Responsibility of the build step</h3>
<ul>
<li><p>Reconstruct documents via the <code>Prebuild</code> method, e.g.: remove some document according to a certain rule.</p>
</li>
<li><p>Transform document content via <code>Build</code> method, e.g.: transform rtf content to html content.</p>
</li>
<li><p>Transform more content required by all document processed via the <code>PostBuild</code> method, e.g.: extract the link text from the title of another document.</p>
</li>
<li><p>About build order:</p>
<ol>
<li>For all documents in one processor always <code>Prebuild</code> -&gt; <code>Build</code> -&gt; <code>Postbuild</code>.</li>
<li>For all documents in one processor always invoke <code>Prebuild</code> by <code>BuildOrder</code>.</li>
<li>For each document in one processor always invoke <code>Build</code> by <code>BuildOrder</code>.</li>
<li>For all documents in one processor always invoke <code>Postbuild</code> by <code>BuildOrder</code>.</li>
</ol>
<p>e.g.: Document processor <em>X</em> has two steps: A (with BuildOrder=1), B (with BuildOrder=2). When <em>X</em> is handling documents [D1, D2, D3], the invoke order is as follows:</p>
<pre><code>A.Prebuild([D1, D2, D3]) returns [D1, D2, D3]
B.Prebuild([D1, D2, D3]) returns [D1, D2, D3]
Parallel(
  A.Build(D1) -&gt; B.Build(D1),
  A.Build(D2) -&gt; B.Build(D2),
  A.Build(D3) -&gt; B.Build(D3)
)
A.Postbuild([D1, D2, D3])
B.Postbuild([D1, D2, D3])
</code></pre>
</li>
</ul>
<h3 id="create-our-rtfbuildstep">Create our RtfBuildStep:</h3>
<ol>
<li><p>Create a new class (RtfBuildStep.cs), and declare it is a build step for <code>RtfDocumentProcessor</code>:</p>
<pre><code class="lang-csharp">[Export(nameof(RtfDocumentProcessor), typeof(IDocumentBuildStep))]
public class RtfBuildStep : IDocumentBuildStep
{
    // todo : implements IDocumentBuildStep.
}
</code></pre>
</li>
<li><p>In the <code>Build</code> method, convert rtf to html:</p>
<pre><code class="lang-csharp" name="Build">private readonly TaskFactory _taskFactory = new TaskFactory(new StaTaskScheduler(1));

public void Build(FileModel model, IHostService host)
{
    string content = (string)((Dictionary&lt;string, object&gt;)model.Content)[&quot;conceptual&quot;];
    content = _taskFactory.StartNew(() =&gt; RtfToHtmlConverter.ConvertRtfToHtml(content)).Result;
    ((Dictionary&lt;string, object&gt;)model.Content)[&quot;conceptual&quot;] = content;
}
</code></pre></li>
<li><p>Implement other methods:</p>
<pre><code class="lang-csharp" name="Others">public int BuildOrder =&gt; 0;

public string Name =&gt; nameof(RtfBuildStep);

public void Postbuild(ImmutableList&lt;FileModel&gt; models, IHostService host)
{
}

public IEnumerable&lt;FileModel&gt; Prebuild(ImmutableList&lt;FileModel&gt; models, IHostService host)
{
    return models;
}
</code></pre></li>
</ol>
<p>View the final <a href="../codesnippet/Rtf/RtfBuildStep.cs">RtfBuildStep.cs</a></p>
<h2 id="enable-plug-in">Enable plug-in</h2>
<ol>
<li>Build our project.</li>
<li>Copy the output dll files to:
<ul>
<li><p>Global: the folder with name <code>Plugins</code> under DocFX.exe</p>
</li>
<li><p>Non-global: the folder with name <code>Plugins</code> under a template folder. Then run <code>DocFX build</code> command with parameter <code>-t {template}</code>.</p>
<p><em>Hint</em>: DocFX can merge templates so create a template that only contains the <code>Plugins</code> folder, then run the command <code>DocFX build</code> with parameter <code>-t {templateForRender},{templateForPlugins}</code>.</p>
</li>
</ul>
</li>
</ol>
<h2 id="build-document">Build document</h2>
<ol>
<li>Run command <code>DocFX init</code> and set the source article with <code>**.rtf</code>.</li>
<li>Run command <code>DocFX build</code>.</li>
</ol>
<div id="disqus_thread"></div>
                <script>
                (function() { // DON'T EDIT BELOW THIS LINE
                var d = document, s = d.createElement('script');
                s.src = 'https://docfx-github.disqus.com/embed.js';
                s.setAttribute('data-timestamp', +new Date());
                (d.head || d.body).appendChild(s);
                })();
                </script>
                <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
            </article>
          </div>
          
          <div class="hidden-sm col-md-2" role="complementary">
            <div class="sideaffix">
              <div class="contribution">
                <ul class="nav">
                  <li>
                    <a href="#disqus_thread" class="contribution-link">0 Comments</a>
                  </li>
                </ul>
              </div>
              <nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
              <!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
              </nav>
            </div>
          </div>
        </div>
      </div>
      
      <footer>
        <div class="grad-bottom"></div>
        <div class="footer">
          <div class="container">
            <span class="pull-right">
              <a href="#top">Back to top</a>
            </span>
            <span>Copyright © 2015-2018 Microsoft<br>Generated by <strong>DocFX</strong></span>
            
          </div>
        </div>
      </footer>
    </div>
    
    <script type="text/javascript" src="../styles/docfx.vendor.js"></script>
    <script type="text/javascript" src="../styles/docfx.js"></script>
    <script type="text/javascript" src="../styles/main.js"></script>
    <script id="dsq-count-scr" src="//docfx-github.disqus.com/count.js" async=""></script>
    
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
    
      ga('create', 'UA-99241001-1', 'auto');
      ga('send', 'pageview');
    
    </script>
  </body>
</html>
